use crate::document::DocumentManager;
use codegraph_core::{GraphSystem, Result as CoreResult};
use tower_lsp::lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind, Range};
use std::sync::Arc;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::sync::{RwLock, Mutex};
use tracing::{debug, warn};
use regex::Regex;

/// Cache entry for hover information
#[derive(Debug, Clone)]
struct HoverCacheEntry {
    hover_info: String,
    timestamp: Instant,
    content_hash: u64,
}

/// Hover information cache
struct HoverCache {
    entries: HashMap<String, HoverCacheEntry>,
    ttl: Duration,
}

impl HoverCache {
    fn new(ttl: Duration) -> Self {
        Self {
            entries: HashMap::new(),
            ttl,
        }
    }

    fn get(&self, key: &str, content_hash: u64) -> Option<String> {
        if let Some(entry) = self.entries.get(key) {
            if entry.timestamp.elapsed() < self.ttl && entry.content_hash == content_hash {
                return Some(entry.hover_info.clone());
            }
        }
        None
    }

    fn insert(&mut self, key: String, hover_info: String, content_hash: u64) {
        let entry = HoverCacheEntry {
            hover_info,
            timestamp: Instant::now(),
            content_hash,
        };
        self.entries.insert(key, entry);
    }

    fn clear(&mut self) {
        self.entries.clear();
    }
}

/// Enhanced hover provider with intelligent type inference and documentation extraction
pub struct HoverProvider {
    document_manager: Arc<DocumentManager>,
    graph_system: Arc<RwLock<Option<GraphSystem>>>,
    cache: Arc<Mutex<HoverCache>>,
    // Pre-compiled regex patterns for better performance
    function_patterns: HashMap<String, Regex>,
    class_patterns: HashMap<String, Regex>,
    variable_patterns: HashMap<String, Regex>,
}

impl HoverProvider {
    pub fn new(
        document_manager: Arc<DocumentManager>,
        graph_system: Arc<RwLock<Option<GraphSystem>>>,
    ) -> Self {
        let cache = Arc::new(Mutex::new(HoverCache::new(Duration::from_secs(60)))); // 1 minute TTL

        // Pre-compile regex patterns for better performance
        let mut function_patterns = HashMap::new();
        let mut class_patterns = HashMap::new();
        let mut variable_patterns = HashMap::new();

        // Python patterns
        function_patterns.insert(
            "python".to_string(),
            Regex::new(r"def\s+(\w+)\s*\([^)]*\)\s*(?:->\s*[^:]+)?:").unwrap(),
        );
        class_patterns.insert(
            "python".to_string(),
            Regex::new(r"class\s+(\w+)(?:\([^)]*\))?:").unwrap(),
        );
        variable_patterns.insert(
            "python".to_string(),
            Regex::new(r"(\w+)\s*[:=]\s*([^#\n]+)").unwrap(),
        );

        // JavaScript/TypeScript patterns
        function_patterns.insert(
            "javascript".to_string(),
            Regex::new(r"function\s+(\w+)\s*\([^)]*\)").unwrap(),
        );
        function_patterns.insert(
            "typescript".to_string(),
            Regex::new(r"function\s+(\w+)\s*\([^)]*\)(?:\s*:\s*[^{]+)?").unwrap(),
        );
        class_patterns.insert(
            "javascript".to_string(),
            Regex::new(r"class\s+(\w+)(?:\s+extends\s+\w+)?").unwrap(),
        );
        class_patterns.insert(
            "typescript".to_string(),
            Regex::new(r"class\s+(\w+)(?:\s+extends\s+\w+)?").unwrap(),
        );

        // Rust patterns
        function_patterns.insert(
            "rust".to_string(),
            Regex::new(r"fn\s+(\w+)\s*\([^)]*\)(?:\s*->\s*[^{]+)?").unwrap(),
        );
        class_patterns.insert(
            "rust".to_string(),
            Regex::new(r"struct\s+(\w+)").unwrap(),
        );

        Self {
            document_manager,
            graph_system,
            cache,
            function_patterns,
            class_patterns,
            variable_patterns,
        }
    }

    pub async fn provide_hover(&self, params: &HoverParams) -> CoreResult<Option<Hover>> {
        let start_time = Instant::now();
        let uri = &params.text_document_position_params.text_document.uri;
        let position = params.text_document_position_params.position;

        debug!("Providing hover for {} at {:?}", uri, position);

        // Get document
        let document = match self.document_manager.get_document(uri) {
            Some(doc) => doc,
            None => {
                warn!("Document not found for hover: {}", uri);
                return Ok(None);
            }
        };

        let (language, word, content, content_hash) = {
            let doc = document.read().await;
            let language = match &doc.language {
                Some(lang) => lang.clone(),
                None => {
                    debug!("No language detected for hover");
                    return Ok(None);
                }
            };

            // Get the word at the cursor position
            let word = match doc.get_word_at_position(position) {
                Ok(Some(word)) => word,
                Ok(None) => return Ok(None),
                Err(e) => {
                    warn!("Failed to get word at position: {}", e);
                    return Ok(None);
                }
            };

            let content = doc.content.clone();
            let content_hash = self.compute_content_hash(&content);

            (language, word, content, content_hash)
        };

        debug!("Hover word: '{}'", word);

        // Check cache first
        let cache_key = format!("{}:{}:{}:{}", uri, position.line, position.character, word);
        {
            let cache = self.cache.lock().await;
            if let Some(cached_info) = cache.get(&cache_key, content_hash) {
                debug!(
                    "Cache hit for hover '{}' ({}ms)",
                    word,
                    start_time.elapsed().as_millis()
                );

                let word_range = {
                    let doc = document.read().await;
                    self.get_word_range(&*doc, position, &word)?
                };

                return Ok(Some(Hover {
                    contents: HoverContents::Markup(MarkupContent {
                        kind: MarkupKind::Markdown,
                        value: cached_info,
                    }),
                    range: Some(word_range),
                }));
            }
        }

        // Get hover information for the word
        let hover_info = self.get_hover_info(&word, &language, &content, position).await?;

        match hover_info {
            Some(info) => {
                // Cache the result
                {
                    let mut cache = self.cache.lock().await;
                    cache.insert(cache_key, info.clone(), content_hash);
                }

                // Calculate the range of the word
                let word_range = {
                    let doc = document.read().await;
                    self.get_word_range(&*doc, position, &word)?
                };

                let elapsed = start_time.elapsed();
                debug!(
                    "Hover provided for '{}' in {}ms",
                    word,
                    elapsed.as_millis()
                );

                Ok(Some(Hover {
                    contents: HoverContents::Markup(MarkupContent {
                        kind: MarkupKind::Markdown,
                        value: info,
                    }),
                    range: Some(word_range),
                }))
            }
            None => Ok(None),
        }
    }

    /// Compute a simple hash for content to use in caching
    fn compute_content_hash(&self, content: &str) -> u64 {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut hasher = DefaultHasher::new();
        content.hash(&mut hasher);
        hasher.finish()
    }

    async fn get_hover_info(
        &self,
        word: &str,
        language: &str,
        content: &str,
        position: tower_lsp::lsp_types::Position,
    ) -> CoreResult<Option<String>> {
        // Check for built-in symbols first (highest priority)
        if let Some(builtin_info) = self.get_builtin_info(word, language) {
            return Ok(Some(builtin_info));
        }

        // Check for keywords
        if let Some(keyword_info) = self.get_keyword_info(word, language) {
            return Ok(Some(keyword_info));
        }

        // Check for user-defined symbols using regex patterns (improved)
        if let Some(symbol_info) = self.get_enhanced_symbol_info(word, language, content).await? {
            return Ok(Some(symbol_info));
        }

        // Check for type information from context
        if let Some(type_info) = self.get_type_info(word, language, content, position) {
            return Ok(Some(type_info));
        }

        // Check for contextual information (fallback)
        if let Some(context_info) = self.get_context_info(word, language, content) {
            return Ok(Some(context_info));
        }

        Ok(None)
    }

    fn get_builtin_info(&self, word: &str, language: &str) -> Option<String> {
        match language {
            "python" => {
                match word {
                    "print" => Some("```python\nprint(*args, sep=' ', end='\\n', file=sys.stdout, flush=False)\n```\n\nPrint objects to the text stream file, separated by sep and followed by end.".to_string()),
                    "len" => Some("```python\nlen(obj) -> int\n```\n\nReturn the length of an object.".to_string()),
                    "str" => Some("```python\nclass str(object='')\n```\n\nReturn a string version of object.".to_string()),
                    "int" => Some("```python\nclass int(x=0)\n```\n\nReturn an integer object constructed from a number or string.".to_string()),
                    "list" => Some("```python\nclass list(iterable=())\n```\n\nBuilt-in mutable sequence.".to_string()),
                    "dict" => Some("```python\nclass dict(**kwargs)\n```\n\nBuilt-in mapping type.".to_string()),
                    "True" => Some("```python\nTrue: bool\n```\n\nBoolean true value.".to_string()),
                    "False" => Some("```python\nFalse: bool\n```\n\nBoolean false value.".to_string()),
                    "None" => Some("```python\nNone: NoneType\n```\n\nThe sole value of the type NoneType.".to_string()),
                    _ => None,
                }
            }
            "javascript" | "typescript" => {
                match word {
                    "console" => Some("```javascript\nconsole: Console\n```\n\nProvides access to the browser's debugging console.".to_string()),
                    "Array" => Some("```javascript\nclass Array<T>\n```\n\nThe Array object is used to store multiple values in a single variable.".to_string()),
                    "Object" => Some("```javascript\nclass Object\n```\n\nThe Object class represents one of JavaScript's data types.".to_string()),
                    "String" => Some("```javascript\nclass String\n```\n\nThe String object is used to represent and manipulate a sequence of characters.".to_string()),
                    "Number" => Some("```javascript\nclass Number\n```\n\nThe Number object represents numerical data.".to_string()),
                    "Boolean" => Some("```javascript\nclass Boolean\n```\n\nThe Boolean object represents a truth value: true or false.".to_string()),
                    "true" => Some("```javascript\ntrue: boolean\n```\n\nBoolean true value.".to_string()),
                    "false" => Some("```javascript\nfalse: boolean\n```\n\nBoolean false value.".to_string()),
                    "null" => Some("```javascript\nnull: null\n```\n\nRepresents the intentional absence of any object value.".to_string()),
                    "undefined" => Some("```javascript\nundefined: undefined\n```\n\nRepresents the value undefined.".to_string()),
                    _ => None,
                }
            }
            "rust" => {
                match word {
                    "Vec" => Some("```rust\nstruct Vec<T>\n```\n\nA contiguous growable array type.".to_string()),
                    "String" => Some("```rust\nstruct String\n```\n\nA UTF-8 encoded, growable string.".to_string()),
                    "Option" => Some("```rust\nenum Option<T> {\n    Some(T),\n    None,\n}\n```\n\nType Option represents an optional value.".to_string()),
                    "Result" => Some("```rust\nenum Result<T, E> {\n    Ok(T),\n    Err(E),\n}\n```\n\nResult is a type that represents either success (Ok) or failure (Err).".to_string()),
                    "Some" => Some("```rust\nSome(T)\n```\n\nSome value of type T.".to_string()),
                    "None" => Some("```rust\nNone\n```\n\nNo value.".to_string()),
                    "Ok" => Some("```rust\nOk(T)\n```\n\nContains the success value.".to_string()),
                    "Err" => Some("```rust\nErr(E)\n```\n\nContains the error value.".to_string()),
                    _ => None,
                }
            }
            _ => None,
        }
    }

    fn get_keyword_info(&self, word: &str, language: &str) -> Option<String> {
        match language {
            "python" => {
                match word {
                    "def" => Some("```python\ndef\n```\n\nDefines a function.".to_string()),
                    "class" => Some("```python\nclass\n```\n\nDefines a class.".to_string()),
                    "if" => Some("```python\nif\n```\n\nConditional statement.".to_string()),
                    "for" => Some("```python\nfor\n```\n\nLoop statement.".to_string()),
                    "while" => Some("```python\nwhile\n```\n\nLoop statement.".to_string()),
                    "import" => Some("```python\nimport\n```\n\nImports a module.".to_string()),
                    "from" => Some("```python\nfrom\n```\n\nImports specific items from a module.".to_string()),
                    "return" => Some("```python\nreturn\n```\n\nReturns a value from a function.".to_string()),
                    _ => None,
                }
            }
            "javascript" | "typescript" => {
                match word {
                    "function" => Some("```javascript\nfunction\n```\n\nDefines a function.".to_string()),
                    "class" => Some("```javascript\nclass\n```\n\nDefines a class.".to_string()),
                    "if" => Some("```javascript\nif\n```\n\nConditional statement.".to_string()),
                    "for" => Some("```javascript\nfor\n```\n\nLoop statement.".to_string()),
                    "while" => Some("```javascript\nwhile\n```\n\nLoop statement.".to_string()),
                    "import" => Some("```javascript\nimport\n```\n\nImports a module.".to_string()),
                    "export" => Some("```javascript\nexport\n```\n\nExports a value from a module.".to_string()),
                    "return" => Some("```javascript\nreturn\n```\n\nReturns a value from a function.".to_string()),
                    "const" => Some("```javascript\nconst\n```\n\nDeclares a constant.".to_string()),
                    "let" => Some("```javascript\nlet\n```\n\nDeclares a block-scoped variable.".to_string()),
                    "var" => Some("```javascript\nvar\n```\n\nDeclares a variable.".to_string()),
                    _ => None,
                }
            }
            "rust" => {
                match word {
                    "fn" => Some("```rust\nfn\n```\n\nDefines a function.".to_string()),
                    "struct" => Some("```rust\nstruct\n```\n\nDefines a struct.".to_string()),
                    "enum" => Some("```rust\nenum\n```\n\nDefines an enum.".to_string()),
                    "impl" => Some("```rust\nimpl\n```\n\nImplements methods for a type.".to_string()),
                    "trait" => Some("```rust\ntrait\n```\n\nDefines a trait.".to_string()),
                    "let" => Some("```rust\nlet\n```\n\nDeclares a variable binding.".to_string()),
                    "mut" => Some("```rust\nmut\n```\n\nMakes a binding mutable.".to_string()),
                    "use" => Some("```rust\nuse\n```\n\nImports items into scope.".to_string()),
                    "match" => Some("```rust\nmatch\n```\n\nPattern matching expression.".to_string()),
                    _ => None,
                }
            }
            _ => None,
        }
    }

    /// Enhanced symbol information extraction using regex patterns
    async fn get_enhanced_symbol_info(&self, word: &str, language: &str, content: &str) -> CoreResult<Option<String>> {
        // Check for function definitions
        if let Some(pattern) = self.function_patterns.get(language) {
            for line in content.lines() {
                if let Some(captures) = pattern.captures(line) {
                    if let Some(func_name) = captures.get(1) {
                        if func_name.as_str() == word {
                            let signature = line.trim();
                            let doc = self.extract_documentation(content, line);
                            return Ok(Some(format!(
                                "```{}\n{}\n```\n\n{}",
                                language,
                                signature,
                                doc.unwrap_or_else(|| "User-defined function.".to_string())
                            )));
                        }
                    }
                }
            }
        }

        // Check for class definitions
        if let Some(pattern) = self.class_patterns.get(language) {
            for line in content.lines() {
                if let Some(captures) = pattern.captures(line) {
                    if let Some(class_name) = captures.get(1) {
                        if class_name.as_str() == word {
                            let signature = line.trim();
                            let doc = self.extract_documentation(content, line);
                            return Ok(Some(format!(
                                "```{}\n{}\n```\n\n{}",
                                language,
                                signature,
                                doc.unwrap_or_else(|| "User-defined class.".to_string())
                            )));
                        }
                    }
                }
            }
        }

        // Check for variable definitions with type annotations
        if let Some(pattern) = self.variable_patterns.get(language) {
            for line in content.lines() {
                if let Some(captures) = pattern.captures(line) {
                    if let Some(var_name) = captures.get(1) {
                        if var_name.as_str() == word {
                            if let Some(value) = captures.get(2) {
                                let inferred_type = self.infer_type_from_value(value.as_str(), language);
                                return Ok(Some(format!(
                                    "```{}\n{}: {}\n```\n\nVariable with inferred type.",
                                    language,
                                    word,
                                    inferred_type
                                )));
                            }
                        }
                    }
                }
            }
        }

        // TODO: Query the graph system for more detailed symbol information
        Ok(None)
    }

    fn get_context_info(&self, word: &str, language: &str, content: &str) -> Option<String> {
        // Provide contextual information based on surrounding code
        let lines: Vec<&str> = content.lines().collect();
        
        for (_i, line) in lines.iter().enumerate() {
            if line.contains(word) {
                // Look for variable assignments
                if line.contains('=') && !line.trim().starts_with('#') && !line.trim().starts_with("//") {
                    if let Some(assignment_info) = self.parse_assignment(line, word, language) {
                        return Some(assignment_info);
                    }
                }
                
                // Look for function calls
                if line.contains(&format!("{}(", word)) {
                    return Some(format!("```{}\n{}\n```\n\nFunction call found in code.", language, line.trim()));
                }
            }
        }

        None
    }

    fn parse_assignment(&self, line: &str, word: &str, language: &str) -> Option<String> {
        if line.contains(&format!("{} =", word)) {
            let parts: Vec<&str> = line.split('=').collect();
            if parts.len() >= 2 {
                let value = parts[1].trim();
                return Some(format!("```{}\n{} = {}\n```\n\nVariable assignment.", language, word, value));
            }
        }
        None
    }

    fn get_word_range(&self, doc: &crate::document::Document, position: tower_lsp::lsp_types::Position, _word: &str) -> CoreResult<Range> {
        let offset = doc.position_to_offset(position)
            .map_err(|e| codegraph_core::CodeGraphError::parse_error(e))?;

        // Find the start of the word
        let start_offset = doc.content[..offset]
            .rfind(|c: char| !c.is_alphanumeric() && c != '_')
            .map(|i| i + 1)
            .unwrap_or(0);

        // Find the end of the word
        let end_offset = doc.content[offset..]
            .find(|c: char| !c.is_alphanumeric() && c != '_')
            .map(|i| offset + i)
            .unwrap_or(doc.content.len());

        let start_pos = doc.offset_to_position(start_offset)
            .map_err(|e| codegraph_core::CodeGraphError::parse_error(e))?;
        let end_pos = doc.offset_to_position(end_offset)
            .map_err(|e| codegraph_core::CodeGraphError::parse_error(e))?;

        Ok(Range::new(start_pos, end_pos))
    }

    /// Extract documentation comments for a given line
    fn extract_documentation(&self, content: &str, target_line: &str) -> Option<String> {
        let lines: Vec<&str> = content.lines().collect();

        // Find the target line
        for (i, line) in lines.iter().enumerate() {
            if line.trim() == target_line.trim() {
                let mut doc_lines = Vec::new();

                // Look for documentation above the target line
                let mut j = i;
                while j > 0 {
                    j -= 1;
                    let prev_line = lines[j].trim();

                    // Python docstrings and comments
                    if prev_line.starts_with("\"\"\"") || prev_line.starts_with("'''") {
                        doc_lines.insert(0, prev_line.trim_matches('"').trim_matches('\''));
                        break;
                    } else if prev_line.starts_with('#') {
                        doc_lines.insert(0, prev_line.trim_start_matches('#').trim());
                    }
                    // JavaScript/TypeScript comments
                    else if prev_line.starts_with("//") {
                        doc_lines.insert(0, prev_line.trim_start_matches('/').trim());
                    } else if prev_line.starts_with("/*") || prev_line.starts_with("*") {
                        doc_lines.insert(0, prev_line.trim_start_matches("/*").trim_start_matches('*').trim());
                    }
                    // Rust comments
                    else if prev_line.starts_with("///") {
                        doc_lines.insert(0, prev_line.trim_start_matches('/').trim());
                    }
                    // Empty line continues the search
                    else if prev_line.is_empty() {
                        continue;
                    }
                    // Non-comment line stops the search
                    else {
                        break;
                    }
                }

                if !doc_lines.is_empty() {
                    return Some(doc_lines.join("\n"));
                }
                break;
            }
        }

        None
    }

    /// Infer type from variable value
    fn infer_type_from_value(&self, value: &str, language: &str) -> String {
        let value = value.trim();

        match language {
            "python" => {
                if value.starts_with('"') || value.starts_with('\'') {
                    "str".to_string()
                } else if value.parse::<i64>().is_ok() {
                    "int".to_string()
                } else if value.parse::<f64>().is_ok() {
                    "float".to_string()
                } else if value == "True" || value == "False" {
                    "bool".to_string()
                } else if value.starts_with('[') {
                    "list".to_string()
                } else if value.starts_with('{') {
                    "dict".to_string()
                } else if value == "None" {
                    "None".to_string()
                } else {
                    "Any".to_string()
                }
            }
            "javascript" | "typescript" => {
                if value.starts_with('"') || value.starts_with('\'') || value.starts_with('`') {
                    "string".to_string()
                } else if value.parse::<i64>().is_ok() || value.parse::<f64>().is_ok() {
                    "number".to_string()
                } else if value == "true" || value == "false" {
                    "boolean".to_string()
                } else if value.starts_with('[') {
                    "Array".to_string()
                } else if value.starts_with('{') {
                    "Object".to_string()
                } else if value == "null" {
                    "null".to_string()
                } else if value == "undefined" {
                    "undefined".to_string()
                } else {
                    "any".to_string()
                }
            }
            "rust" => {
                if value.starts_with('"') {
                    "&str".to_string()
                } else if value.parse::<i64>().is_ok() {
                    "i32".to_string()
                } else if value.parse::<f64>().is_ok() {
                    "f64".to_string()
                } else if value == "true" || value == "false" {
                    "bool".to_string()
                } else if value.starts_with("vec![") || value.starts_with("Vec::") {
                    "Vec<T>".to_string()
                } else if value == "None" {
                    "Option<T>".to_string()
                } else {
                    "unknown".to_string()
                }
            }
            _ => "unknown".to_string(),
        }
    }

    /// Get type information from context analysis
    fn get_type_info(
        &self,
        word: &str,
        language: &str,
        content: &str,
        position: tower_lsp::lsp_types::Position,
    ) -> Option<String> {
        let lines: Vec<&str> = content.lines().collect();
        let current_line_idx = position.line as usize;

        if current_line_idx >= lines.len() {
            return None;
        }

        // Look for type annotations in the current and surrounding lines
        for i in current_line_idx.saturating_sub(5)..=std::cmp::min(current_line_idx + 5, lines.len() - 1) {
            let line = lines[i];

            // TypeScript/JavaScript type annotations
            if language == "typescript" && line.contains(&format!("{}: ", word)) {
                if let Some(type_start) = line.find(&format!("{}: ", word)) {
                    let after_colon = &line[type_start + word.len() + 2..];
                    if let Some(type_end) = after_colon.find(&[' ', '=', ';', ',', ')', '}'][..]) {
                        let type_annotation = after_colon[..type_end].trim();
                        return Some(format!(
                            "```typescript\n{}: {}\n```\n\nType annotation found.",
                            word,
                            type_annotation
                        ));
                    }
                }
            }

            // Python type hints
            if language == "python" && line.contains(&format!("{}: ", word)) {
                if let Some(type_start) = line.find(&format!("{}: ", word)) {
                    let after_colon = &line[type_start + word.len() + 2..];
                    if let Some(type_end) = after_colon.find(&[' ', '=', '\n'][..]) {
                        let type_hint = after_colon[..type_end].trim();
                        return Some(format!(
                            "```python\n{}: {}\n```\n\nType hint found.",
                            word,
                            type_hint
                        ));
                    }
                }
            }
        }

        None
    }
}
